Micron Document
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
| SparkN0de-git | SparkN0de |
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------


Displaying Raw • Download


meshchatx/src/backend/database/messages.py a8a9eb3ff9168e00926505dbf1f16b6c85c85c57 (a8a9eb3f) Text, 23.63 KB

T8b949e# SPDX-License-Identifier: 0BSD

Tff7b72import T7ee787json
Tff7b72from T7ee787datetime Tff7b72import Te6edf3UTCTb4b4b4, Te6edf3datetime

Tff7b72from T7ee787.T7ee787provider Tff7b72import Te6edf3DatabaseProvider


Tff7b72class T56d364MessageDAOTb4b4b4:
Tff7b72def Tff7b72__init__Tb4b4b4(Tff7b72selfTb4b4b4, Te6edf3providerTb4b4b4: Te6edf3DatabaseProviderTb4b4b4)Tb4b4b4:
Tff7b72selfTff7b72.Td2a8ffprovider Tff7b72= Te6edf3provider

Tff7b72def Td2a8ffupsert_lxmf_messageTb4b4b4(Tff7b72selfTb4b4b4, Te6edf3dataTb4b4b4)Tb4b4b4:
T8b949e# Ensure data is a dict if it's a sqlite3.Row
Tff7b72if Tff7b72not Tffa657isinstanceTb4b4b4(Te6edf3dataTb4b4b4, Tffa657dictTb4b4b4)Tb4b4b4:
Te6edf3data Tff7b72= Tffa657dictTb4b4b4(Te6edf3dataTb4b4b4)

T8b949e# Ensure all required fields are present and handle defaults
Te6edf3fields Tff7b72= Tb4b4b4[
Ta5d6ff"Ta5d6ffhashTa5d6ff"Tb4b4b4,
Ta5d6ff"Ta5d6ffsource_hashTa5d6ff"Tb4b4b4,
Ta5d6ff"Ta5d6ffdestination_hashTa5d6ff"Tb4b4b4,
Ta5d6ff"Ta5d6ffpeer_hashTa5d6ff"Tb4b4b4,
Ta5d6ff"Ta5d6ffstateTa5d6ff"Tb4b4b4,
Ta5d6ff"Ta5d6ffprogressTa5d6ff"Tb4b4b4,
Ta5d6ff"Ta5d6ffis_incomingTa5d6ff"Tb4b4b4,
Ta5d6ff"Ta5d6ffmethodTa5d6ff"Tb4b4b4,
Ta5d6ff"Ta5d6ffdelivery_attemptsTa5d6ff"Tb4b4b4,
Ta5d6ff"Ta5d6ffnext_delivery_attempt_atTa5d6ff"Tb4b4b4,
Ta5d6ff"Ta5d6fftitleTa5d6ff"Tb4b4b4,
Ta5d6ff"Ta5d6ffcontentTa5d6ff"Tb4b4b4,
Ta5d6ff"Ta5d6fffieldsTa5d6ff"Tb4b4b4,
Ta5d6ff"Ta5d6fftimestampTa5d6ff"Tb4b4b4,
Ta5d6ff"Ta5d6ffrssiTa5d6ff"Tb4b4b4,
Ta5d6ff"Ta5d6ffsnrTa5d6ff"Tb4b4b4,
Ta5d6ff"Ta5d6ffqualityTa5d6ff"Tb4b4b4,
Ta5d6ff"Ta5d6ffis_spamTa5d6ff"Tb4b4b4,
Ta5d6ff"Ta5d6ffreply_to_hashTa5d6ff"Tb4b4b4,
Ta5d6ff"Ta5d6ffattachments_strippedTa5d6ff"Tb4b4b4,
Ta5d6ff"Ta5d6ffpath_hops_at_sendTa5d6ff"Tb4b4b4,
Ta5d6ff"Ta5d6ffpath_interface_at_sendTa5d6ff"Tb4b4b4,
Ta5d6ff"Ta5d6ffpath_finding_measureTa5d6ff"Tb4b4b4,
Ta5d6ff"Ta5d6ffpath_row_hash_hexTa5d6ff"Tb4b4b4,
Tb4b4b4]

Te6edf3columns Tff7b72= Ta5d6ff"Ta5d6ff, Ta5d6ff"Tff7b72.Td2a8ffjoinTb4b4b4(Te6edf3fieldsTb4b4b4)
Te6edf3placeholders Tff7b72= Ta5d6ff"Ta5d6ff, Ta5d6ff"Tff7b72.Td2a8ffjoinTb4b4b4(Tb4b4b4[Ta5d6ff"Ta5d6ff?Ta5d6ff"Tb4b4b4] Tff7b72* Tffa657lenTb4b4b4(Te6edf3fieldsTb4b4b4)Tb4b4b4)
Te6edf3update_fields Tff7b72= Tb4b4b4[
Te6edf3f
Tff7b72for Te6edf3f Tff7b72in Te6edf3fields
Tff7b72if Te6edf3f Tff7b72!= Ta5d6ff"Ta5d6ffhashTa5d6ff"
Tff7b72and Te6edf3f
Tff7b72not Tff7b72in Tb4b4b4(
Ta5d6ff"Ta5d6ffpath_hops_at_sendTa5d6ff"Tb4b4b4,
Ta5d6ff"Ta5d6ffpath_interface_at_sendTa5d6ff"Tb4b4b4,
Ta5d6ff"Ta5d6ffpath_finding_measureTa5d6ff"Tb4b4b4,
Ta5d6ff"Ta5d6ffpath_row_hash_hexTa5d6ff"Tb4b4b4,
Tb4b4b4)
Tb4b4b4]
Te6edf3update_set Tff7b72= Ta5d6ff"Ta5d6ff, Ta5d6ff"Tff7b72.Td2a8ffjoinTb4b4b4(Tb4b4b4[Ta5d6fffTa5d6ff"Tffd700{Te6edf3fTffd700}Ta5d6ff = EXCLUDED.Tffd700{Te6edf3fTffd700}Ta5d6ff" Tff7b72for Te6edf3f Tff7b72in Te6edf3update_fieldsTb4b4b4]Tb4b4b4)

Te6edf3query Tff7b72= Tb4b4b4(
Ta5d6fffTa5d6ff"Ta5d6ffINSERT INTO lxmf_messages (Tffd700{Te6edf3columnsTffd700}Ta5d6ff, created_at, updated_at) VALUES (Tffd700{Te6edf3placeholdersTffd700}Ta5d6ff, ?, ?) Ta5d6ff"
Ta5d6fffTa5d6ff"Ta5d6ffON CONFLICT(hash) DO UPDATE SET Tffd700{Te6edf3update_setTffd700}Ta5d6ff, updated_at = EXCLUDED.updated_atTa5d6ff"
Tb4b4b4)

Te6edf3params Tff7b72= Tb4b4b4[Tb4b4b4]
Tff7b72for Te6edf3f Tff7b72in Te6edf3fieldsTb4b4b4:
Te6edf3val Tff7b72= Te6edf3dataTff7b72.Td2a8ffgetTb4b4b4(Te6edf3fTb4b4b4)
Tff7b72if Te6edf3f Tff7b72== Ta5d6ff"Ta5d6fffieldsTa5d6ff" Tff7b72and Tffa657isinstanceTb4b4b4(Te6edf3valTb4b4b4, Tffa657dictTb4b4b4)Tb4b4b4:
Te6edf3val Tff7b72= Te6edf3jsonTff7b72.Td2a8ffdumpsTb4b4b4(Te6edf3valTb4b4b4)
Te6edf3paramsTff7b72.Td2a8ffappendTb4b4b4(Te6edf3valTb4b4b4)

Te6edf3now Tff7b72= Te6edf3datetimeTff7b72.Td2a8ffnowTb4b4b4(Te6edf3UTCTb4b4b4)Tff7b72.Td2a8ffisoformatTb4b4b4(Tb4b4b4)
Te6edf3paramsTff7b72.Td2a8ffappendTb4b4b4(Te6edf3nowTb4b4b4)
Te6edf3paramsTff7b72.Td2a8ffappendTb4b4b4(Te6edf3nowTb4b4b4)

Tff7b72selfTff7b72.Td2a8ffproviderTff7b72.Td2a8ffexecuteTb4b4b4(Te6edf3queryTb4b4b4, Te6edf3paramsTb4b4b4)

Tff7b72def Td2a8ffset_lxmf_message_path_at_send_if_unsetTb4b4b4(
Tff7b72selfTb4b4b4, Te6edf3message_hashTb4b4b4, Te6edf3hopsTb4b4b4, Te6edf3interface_name
Tb4b4b4)Tb4b4b4:
T8b949e"""Store Reticulum path snapshot once (send or receive); never overwrites."""
Te6edf3now Tff7b72= Te6edf3datetimeTff7b72.Td2a8ffnowTb4b4b4(Te6edf3UTCTb4b4b4)Tff7b72.Td2a8ffisoformatTb4b4b4(Tb4b4b4)
Tff7b72selfTff7b72.Td2a8ffproviderTff7b72.Td2a8ffexecuteTb4b4b4(
Ta5d6ff"Ta5d6ffUPDATE lxmf_messages SET path_hops_at_send = ?, path_interface_at_send = ?, updated_at = ? Ta5d6ff"
Ta5d6ff"Ta5d6ffWHERE hash = ? AND path_hops_at_send IS NULLTa5d6ff"Tb4b4b4,
Tb4b4b4(Te6edf3hopsTb4b4b4, Te6edf3interface_nameTb4b4b4, Te6edf3nowTb4b4b4, Te6edf3message_hashTb4b4b4)Tb4b4b4,
Tb4b4b4)

Tff7b72def Td2a8ffupdate_lxmf_message_stateTb4b4b4(
Tff7b72selfTb4b4b4,
Te6edf3message_hashTb4b4b4,
Te6edf3stateTb4b4b4,
Te6edf3progressTb4b4b4,
Te6edf3delivery_attemptsTb4b4b4,
Te6edf3next_delivery_attempt_atTb4b4b4,
Te6edf3rssiTff7b72=Tff7b72NoneTb4b4b4,
Te6edf3snrTff7b72=Tff7b72NoneTb4b4b4,
Te6edf3qualityTff7b72=Tff7b72NoneTb4b4b4,
Te6edf3methodTff7b72=Tff7b72NoneTb4b4b4,
Tb4b4b4)Tb4b4b4:
T8b949e"""Lightweight update for delivery-state changes only.

Avoids re-serializing the full message (including base64 attachment
data) which the heavy ``upsert_lxmf_message`` path does.
"""
Te6edf3now Tff7b72= Te6edf3datetimeTff7b72.Td2a8ffnowTb4b4b4(Te6edf3UTCTb4b4b4)Tff7b72.Td2a8ffisoformatTb4b4b4(Tb4b4b4)
Tff7b72if Te6edf3method Tff7b72is Tff7b72NoneTb4b4b4:
Tff7b72selfTff7b72.Td2a8ffproviderTff7b72.Td2a8ffexecuteTb4b4b4(
Ta5d6ff"Ta5d6ffUPDATE lxmf_messages SET state = ?, progress = ?, Ta5d6ff"
Ta5d6ff"Ta5d6ffdelivery_attempts = ?, next_delivery_attempt_at = ?, Ta5d6ff"
Ta5d6ff"Ta5d6ffrssi = ?, snr = ?, quality = ?, updated_at = ? Ta5d6ff"
Ta5d6ff"Ta5d6ffWHERE hash = ?Ta5d6ff"Tb4b4b4,
Tb4b4b4(
Te6edf3stateTb4b4b4,
Te6edf3progressTb4b4b4,
Te6edf3delivery_attemptsTb4b4b4,
Te6edf3next_delivery_attempt_atTb4b4b4,
Te6edf3rssiTb4b4b4,
Te6edf3snrTb4b4b4,
Te6edf3qualityTb4b4b4,
Te6edf3nowTb4b4b4,
Te6edf3message_hashTb4b4b4,
Tb4b4b4)Tb4b4b4,
Tb4b4b4)
Tff7b72elseTb4b4b4:
Tff7b72selfTff7b72.Td2a8ffproviderTff7b72.Td2a8ffexecuteTb4b4b4(
Ta5d6ff"Ta5d6ffUPDATE lxmf_messages SET state = ?, progress = ?, Ta5d6ff"
Ta5d6ff"Ta5d6ffdelivery_attempts = ?, next_delivery_attempt_at = ?, Ta5d6ff"
Ta5d6ff"Ta5d6ffrssi = ?, snr = ?, quality = ?, method = ?, updated_at = ? Ta5d6ff"
Ta5d6ff"Ta5d6ffWHERE hash = ?Ta5d6ff"Tb4b4b4,
Tb4b4b4(
Te6edf3stateTb4b4b4,
Te6edf3progressTb4b4b4,
Te6edf3delivery_attemptsTb4b4b4,
Te6edf3next_delivery_attempt_atTb4b4b4,
Te6edf3rssiTb4b4b4,
Te6edf3snrTb4b4b4,
Te6edf3qualityTb4b4b4,
Te6edf3methodTb4b4b4,
Te6edf3nowTb4b4b4,
Te6edf3message_hashTb4b4b4,
Tb4b4b4)Tb4b4b4,
Tb4b4b4)

Tff7b72def Td2a8ffget_lxmf_message_by_hashTb4b4b4(Tff7b72selfTb4b4b4, Te6edf3message_hashTb4b4b4)Tb4b4b4:
Tff7b72return Tff7b72selfTff7b72.Td2a8ffproviderTff7b72.Td2a8fffetchoneTb4b4b4(
Ta5d6ff"Ta5d6ffSELECT * FROM lxmf_messages WHERE hash = ?Ta5d6ff"Tb4b4b4,
Tb4b4b4(Te6edf3message_hashTb4b4b4,Tb4b4b4)Tb4b4b4,
Tb4b4b4)

Tff7b72def Td2a8fflist_message_hashes_for_peerTb4b4b4(Tff7b72selfTb4b4b4, Te6edf3peer_hashTb4b4b4)Tb4b4b4:
Te6edf3rows Tff7b72= Tff7b72selfTff7b72.Td2a8ffproviderTff7b72.Td2a8fffetchallTb4b4b4(
Ta5d6ff"Ta5d6ffSELECT hash FROM lxmf_messages WHERE peer_hash = ?Ta5d6ff"Tb4b4b4,
Tb4b4b4(Te6edf3peer_hashTb4b4b4,Tb4b4b4)Tb4b4b4,
Tb4b4b4)
Tff7b72return Tb4b4b4[Te6edf3rTb4b4b4[Ta5d6ff"Ta5d6ffhashTa5d6ff"Tb4b4b4] Tff7b72for Te6edf3r Tff7b72in Te6edf3rowsTb4b4b4]

Tff7b72def Td2a8ffget_pinned_peer_hashesTb4b4b4(Tff7b72selfTb4b4b4)Tb4b4b4:
Te6edf3rows Tff7b72= Tff7b72selfTff7b72.Td2a8ffproviderTff7b72.Td2a8fffetchallTb4b4b4(
Ta5d6ff"Ta5d6ffSELECT peer_hash FROM lxmf_conversation_pins ORDER BY pinned_at DESCTa5d6ff"Tb4b4b4,
Tb4b4b4)
Tff7b72return Tb4b4b4[Te6edf3rTb4b4b4[Ta5d6ff"Ta5d6ffpeer_hashTa5d6ff"Tb4b4b4] Tff7b72for Te6edf3r Tff7b72in Te6edf3rowsTb4b4b4]

Tff7b72def Td2a8ffis_peer_pinnedTb4b4b4(Tff7b72selfTb4b4b4, Te6edf3peer_hashTb4b4b4)Tb4b4b4:
Te6edf3row Tff7b72= Tff7b72selfTff7b72.Td2a8ffproviderTff7b72.Td2a8fffetchoneTb4b4b4(
Ta5d6ff"Ta5d6ffSELECT 1 AS ok FROM lxmf_conversation_pins WHERE peer_hash = ?Ta5d6ff"Tb4b4b4,
Tb4b4b4(Te6edf3peer_hashTb4b4b4,Tb4b4b4)Tb4b4b4,
Tb4b4b4)
Tff7b72return Te6edf3row Tff7b72is Tff7b72not Tff7b72None

Tff7b72def Td2a8ffset_peer_pinnedTb4b4b4(Tff7b72selfTb4b4b4, Te6edf3peer_hashTb4b4b4, Te6edf3pinnedTb4b4b4)Tb4b4b4:
Tff7b72if Te6edf3pinnedTb4b4b4:
Tff7b72selfTff7b72.Td2a8ffproviderTff7b72.Td2a8ffexecuteTb4b4b4(
T8b949e"""
INSERT INTO lxmf_conversation_pins (peer_hash, pinned_at)
VALUES (?, strftime('%s', 'now'))
ON CONFLICT(peer_hash) DO UPDATE SET pinned_at = EXCLUDED.pinned_at
"""Tb4b4b4,
Tb4b4b4(Te6edf3peer_hashTb4b4b4,Tb4b4b4)Tb4b4b4,
Tb4b4b4)
Tff7b72elseTb4b4b4:
Tff7b72selfTff7b72.Td2a8ffproviderTff7b72.Td2a8ffexecuteTb4b4b4(
Ta5d6ff"Ta5d6ffDELETE FROM lxmf_conversation_pins WHERE peer_hash = ?Ta5d6ff"Tb4b4b4,
Tb4b4b4(Te6edf3peer_hashTb4b4b4,Tb4b4b4)Tb4b4b4,
Tb4b4b4)

Tff7b72def Td2a8fftoggle_peer_pinTb4b4b4(Tff7b72selfTb4b4b4, Te6edf3peer_hashTb4b4b4)Tb4b4b4:
Tff7b72if Tff7b72selfTff7b72.Td2a8ffis_peer_pinnedTb4b4b4(Te6edf3peer_hashTb4b4b4)Tb4b4b4:
Tff7b72selfTff7b72.Td2a8ffset_peer_pinnedTb4b4b4(Te6edf3peer_hashTb4b4b4, Tff7b72FalseTb4b4b4)
Tff7b72return Tff7b72False
Tff7b72selfTff7b72.Td2a8ffset_peer_pinnedTb4b4b4(Te6edf3peer_hashTb4b4b4, Tff7b72TrueTb4b4b4)
Tff7b72return Tff7b72True

Tff7b72def Td2a8fflist_message_hashes_with_timestamp_beforeTb4b4b4(Tff7b72selfTb4b4b4, Te6edf3cutoff_tsTb4b4b4: Tffa657floatTb4b4b4) Tff7b72-Tff7b72> Tffa657listTb4b4b4[Tffa657strTb4b4b4]Tb4b4b4:
Te6edf3rows Tff7b72= Tff7b72selfTff7b72.Td2a8ffproviderTff7b72.Td2a8fffetchallTb4b4b4(
Ta5d6ff"Ta5d6ffSELECT hash FROM lxmf_messages WHERE timestamp IS NOT NULL AND timestamp < ?Ta5d6ff"Tb4b4b4,
Tb4b4b4(Te6edf3cutoff_tsTb4b4b4,Tb4b4b4)Tb4b4b4,
Tb4b4b4)
Tff7b72return Tb4b4b4[Te6edf3rTb4b4b4[Ta5d6ff"Ta5d6ffhashTa5d6ff"Tb4b4b4] Tff7b72for Te6edf3r Tff7b72in Te6edf3rows Tff7b72if Te6edf3rTff7b72.Td2a8ffgetTb4b4b4(Ta5d6ff"Ta5d6ffhashTa5d6ff"Tb4b4b4)Tb4b4b4]

Tff7b72def Td2a8ffprune_conversation_metadata_for_peers_with_no_messagesTb4b4b4(Tff7b72selfTb4b4b4) Tff7b72-Tff7b72> Tff7b72NoneTb4b4b4:
Tff7b72selfTff7b72.Td2a8ffproviderTff7b72.Td2a8ffexecuteTb4b4b4(
T8b949e"""
DELETE FROM lxmf_conversation_read_state
WHERE destination_hash NOT IN (
SELECT DISTINCT peer_hash FROM lxmf_messages WHERE peer_hash IS NOT NULL
)
"""Tb4b4b4,
Tb4b4b4)
Tff7b72selfTff7b72.Td2a8ffproviderTff7b72.Td2a8ffexecuteTb4b4b4(
T8b949e"""
DELETE FROM lxmf_conversation_folders
WHERE peer_hash NOT IN (
SELECT DISTINCT peer_hash FROM lxmf_messages WHERE peer_hash IS NOT NULL
)
"""Tb4b4b4,
Tb4b4b4)
Tff7b72selfTff7b72.Td2a8ffproviderTff7b72.Td2a8ffexecuteTb4b4b4(
T8b949e"""
DELETE FROM lxmf_conversation_pins
WHERE peer_hash NOT IN (
SELECT DISTINCT peer_hash FROM lxmf_messages WHERE peer_hash IS NOT NULL
)
"""Tb4b4b4,
Tb4b4b4)

Tff7b72def Td2a8ffdelete_lxmf_messages_by_hashesTb4b4b4(Tff7b72selfTb4b4b4, Te6edf3message_hashesTb4b4b4)Tb4b4b4:
Tff7b72if Tff7b72not Te6edf3message_hashesTb4b4b4:
Tff7b72return
Te6edf3placeholders Tff7b72= Ta5d6ff"Ta5d6ff, Ta5d6ff"Tff7b72.Td2a8ffjoinTb4b4b4(Tb4b4b4[Ta5d6ff"Ta5d6ff?Ta5d6ff"Tb4b4b4] Tff7b72* Tffa657lenTb4b4b4(Te6edf3message_hashesTb4b4b4)Tb4b4b4)
Tff7b72selfTff7b72.Td2a8ffproviderTff7b72.Td2a8ffexecuteTb4b4b4(
Ta5d6fffTa5d6ff"Ta5d6ffDELETE FROM lxmf_messages WHERE hash IN (Tffd700{Te6edf3placeholdersTffd700}Ta5d6ff)Ta5d6ff"Tb4b4b4,
Tffa657tupleTb4b4b4(Te6edf3message_hashesTb4b4b4)Tb4b4b4,
Tb4b4b4)

Tff7b72def Td2a8ffdelete_lxmf_message_by_hashTb4b4b4(Tff7b72selfTb4b4b4, Te6edf3message_hashTb4b4b4)Tb4b4b4:
Tff7b72selfTff7b72.Td2a8ffproviderTff7b72.Td2a8ffexecuteTb4b4b4(
Ta5d6ff"Ta5d6ffDELETE FROM lxmf_messages WHERE hash = ?Ta5d6ff"Tb4b4b4,
Tb4b4b4(Te6edf3message_hashTb4b4b4,Tb4b4b4)Tb4b4b4,
Tb4b4b4)

Tff7b72def Td2a8ffdelete_all_lxmf_messagesTb4b4b4(Tff7b72selfTb4b4b4)Tb4b4b4:
Tff7b72with Tff7b72selfTff7b72.Td2a8ffproviderTb4b4b4:
Tff7b72selfTff7b72.Td2a8ffproviderTff7b72.Td2a8ffexecuteTb4b4b4(Ta5d6ff"Ta5d6ffDELETE FROM lxmf_messagesTa5d6ff"Tb4b4b4)
Tff7b72selfTff7b72.Td2a8ffproviderTff7b72.Td2a8ffexecuteTb4b4b4(Ta5d6ff"Ta5d6ffDELETE FROM lxmf_conversation_read_stateTa5d6ff"Tb4b4b4)

Tff7b72def Td2a8ffget_all_lxmf_messagesTb4b4b4(Tff7b72selfTb4b4b4, Te6edf3limitTff7b72=T79c0ff5000Tb4b4b4, Te6edf3offsetTff7b72=T79c0ff0Tb4b4b4)Tb4b4b4:
Tff7b72return Tff7b72selfTff7b72.Td2a8ffproviderTff7b72.Td2a8fffetchallTb4b4b4(
Ta5d6ff"Ta5d6ffSELECT * FROM lxmf_messages ORDER BY id LIMIT ? OFFSET ?Ta5d6ff"Tb4b4b4,
Tb4b4b4(Te6edf3limitTb4b4b4, Te6edf3offsetTb4b4b4)Tb4b4b4,
Tb4b4b4)

Tff7b72def Td2a8ffcount_lxmf_messagesTb4b4b4(Tff7b72selfTb4b4b4)Tb4b4b4:
Te6edf3row Tff7b72= Tff7b72selfTff7b72.Td2a8ffproviderTff7b72.Td2a8fffetchoneTb4b4b4(Ta5d6ff"Ta5d6ffSELECT COUNT(*) AS count FROM lxmf_messagesTa5d6ff"Tb4b4b4)
Tff7b72return Te6edf3rowTb4b4b4[Ta5d6ff"Ta5d6ffcountTa5d6ff"Tb4b4b4] Tff7b72if Te6edf3row Tff7b72and Te6edf3rowTb4b4b4[Ta5d6ff"Ta5d6ffcountTa5d6ff"Tb4b4b4] Tff7b72is Tff7b72not Tff7b72None Tff7b72else T79c0ff0

Tff7b72def Td2a8ffcount_lxmf_messages_by_stateTb4b4b4(Tff7b72selfTb4b4b4, Te6edf3stateTb4b4b4)Tb4b4b4:
Te6edf3row Tff7b72= Tff7b72selfTff7b72.Td2a8ffproviderTff7b72.Td2a8fffetchoneTb4b4b4(
Ta5d6ff"Ta5d6ffSELECT COUNT(*) AS count FROM lxmf_messages WHERE state = ?Ta5d6ff"Tb4b4b4,
Tb4b4b4(Te6edf3stateTb4b4b4,Tb4b4b4)Tb4b4b4,
Tb4b4b4)
Tff7b72return Te6edf3rowTb4b4b4[Ta5d6ff"Ta5d6ffcountTa5d6ff"Tb4b4b4] Tff7b72if Te6edf3row Tff7b72and Te6edf3rowTb4b4b4[Ta5d6ff"Ta5d6ffcountTa5d6ff"Tb4b4b4] Tff7b72is Tff7b72not Tff7b72None Tff7b72else T79c0ff0

Tff7b72def Td2a8ffget_conversation_messagesTb4b4b4(Tff7b72selfTb4b4b4, Te6edf3destination_hashTb4b4b4, Te6edf3limitTff7b72=T79c0ff100Tb4b4b4, Te6edf3offsetTff7b72=T79c0ff0Tb4b4b4)Tb4b4b4:
Tff7b72return Tff7b72selfTff7b72.Td2a8ffproviderTff7b72.Td2a8fffetchallTb4b4b4(
Ta5d6ff"Ta5d6ffSELECT * FROM lxmf_messages WHERE peer_hash = ? ORDER BY timestamp DESC LIMIT ? OFFSET ?Ta5d6ff"Tb4b4b4,
Tb4b4b4(Te6edf3destination_hashTb4b4b4, Te6edf3limitTb4b4b4, Te6edf3offsetTb4b4b4)Tb4b4b4,
Tb4b4b4)

Tff7b72def Td2a8ffget_latest_user_facing_incoming_messageTb4b4b4(Tff7b72selfTb4b4b4, Te6edf3peer_hashTb4b4b4, Tff7b72*Tb4b4b4, Te6edf3scan_limitTff7b72=T79c0ff50Tb4b4b4)Tb4b4b4:
T8b949e"""Return the most recent incoming user-facing message for ``peer_hash``.

Walks recent incoming messages in timestamp-descending order and applies
:func:`is_user_facing_lxmf_payload` in Python (the SQLite layer cannot
cheaply parse the JSON ``fields`` blob). ``scan_limit`` bounds the walk
so a long chain of reactions/telemetry won't degrade the bell endpoint.

Returns ``None`` if no user-facing incoming message exists in the
scanned window.
"""
Tff7b72from T7ee787meshchatxT7ee787.T7ee787srcT7ee787.T7ee787backendT7ee787.T7ee787lxmf_utils Tff7b72import Te6edf3is_user_facing_lxmf_payload

Te6edf3rows Tff7b72= Tff7b72selfTff7b72.Td2a8ffproviderTff7b72.Td2a8fffetchallTb4b4b4(
Ta5d6ff"Ta5d6ffSELECT id, hash, peer_hash, source_hash, destination_hash, Ta5d6ff"
Ta5d6ff"Ta5d6ffis_incoming, title, content, fields, timestamp Ta5d6ff"
Ta5d6ff"Ta5d6ffFROM lxmf_messages WHERE peer_hash = ? AND is_incoming = 1 Ta5d6ff"
Ta5d6ff"Ta5d6ffORDER BY timestamp DESC LIMIT ?Ta5d6ff"Tb4b4b4,
Tb4b4b4(Te6edf3peer_hashTb4b4b4, Te6edf3scan_limitTb4b4b4)Tb4b4b4,
Tb4b4b4)
Tff7b72for Te6edf3row Tff7b72in Te6edf3rowsTb4b4b4:
Te6edf3row_dict Tff7b72= Tffa657dictTb4b4b4(Te6edf3rowTb4b4b4) Tff7b72if Tff7b72not Tffa657isinstanceTb4b4b4(Te6edf3rowTb4b4b4, Tffa657dictTb4b4b4) Tff7b72else Te6edf3row
Tff7b72if Te6edf3is_user_facing_lxmf_payloadTb4b4b4(
Te6edf3row_dictTff7b72.Td2a8ffgetTb4b4b4(Ta5d6ff"Ta5d6fffieldsTa5d6ff"Tb4b4b4)Tb4b4b4,
Te6edf3row_dictTff7b72.Td2a8ffgetTb4b4b4(Ta5d6ff"Ta5d6ffcontentTa5d6ff"Tb4b4b4)Tb4b4b4,
Te6edf3row_dictTff7b72.Td2a8ffgetTb4b4b4(Ta5d6ff"Ta5d6fftitleTa5d6ff"Tb4b4b4)Tb4b4b4,
Tb4b4b4)Tb4b4b4:
Tff7b72return Te6edf3row_dict
Tff7b72return Tff7b72None

Te6edf3CONVERSATION_LIST_COLUMNS Tff7b72= Tb4b4b4(
Ta5d6ff"Ta5d6ffm1.id, m1.hash, m1.source_hash, m1.destination_hash, m1.peer_hash, Ta5d6ff"
Ta5d6ff"Ta5d6ffm1.state, m1.is_incoming, m1.title, m1.timestamp, m1.created_at, m1.updated_atTa5d6ff"
Tb4b4b4)

Tff7b72def Td2a8ffget_conversationsTb4b4b4(Tff7b72selfTb4b4b4)Tb4b4b4:
Te6edf3query Tff7b72= Ta5d6fffTa5d6ff"""
Ta5d6ff SELECT Tffd700{Tff7b72selfTff7b72.Td2a8ffCONVERSATION_LIST_COLUMNSTffd700}Ta5d6ff FROM lxmf_messages m1
Ta5d6ff INNER JOIN (
Ta5d6ff SELECT peer_hash, MAX(timestamp) as max_ts
Ta5d6ff FROM lxmf_messages
Ta5d6ff WHERE peer_hash IS NOT NULL
Ta5d6ff GROUP BY peer_hash
Ta5d6ff ) m2 ON m1.peer_hash = m2.peer_hash AND m1.timestamp = m2.max_ts
Ta5d6ff GROUP BY m1.peer_hash
Ta5d6ff ORDER BY m1.timestamp DESC
Ta5d6ff Ta5d6ff"""
Tff7b72return Tff7b72selfTff7b72.Td2a8ffproviderTff7b72.Td2a8fffetchallTb4b4b4(Te6edf3queryTb4b4b4)

Tff7b72def Td2a8ffmark_conversation_as_readTb4b4b4(Tff7b72selfTb4b4b4, Te6edf3destination_hashTb4b4b4)Tb4b4b4:
Te6edf3now Tff7b72= Te6edf3datetimeTff7b72.Td2a8ffnowTb4b4b4(Te6edf3UTCTb4b4b4)Tff7b72.Td2a8ffisoformatTb4b4b4(Tb4b4b4)
Tff7b72selfTff7b72.Td2a8ffproviderTff7b72.Td2a8ffexecuteTb4b4b4(
T8b949e"""
INSERT INTO lxmf_conversation_read_state (destination_hash, last_read_at, created_at, updated_at)
VALUES (?, ?, ?, ?)
ON CONFLICT(destination_hash) DO UPDATE SET
last_read_at = EXCLUDED.last_read_at,
updated_at = EXCLUDED.updated_at
"""Tb4b4b4,
Tb4b4b4(Te6edf3destination_hashTb4b4b4, Te6edf3nowTb4b4b4, Te6edf3nowTb4b4b4, Te6edf3nowTb4b4b4)Tb4b4b4,
Tb4b4b4)

Tff7b72def Td2a8ffmark_conversations_as_readTb4b4b4(Tff7b72selfTb4b4b4, Te6edf3destination_hashesTb4b4b4)Tb4b4b4:
Tff7b72if Tff7b72not Te6edf3destination_hashesTb4b4b4:
Tff7b72return
Te6edf3now Tff7b72= Te6edf3datetimeTff7b72.Td2a8ffnowTb4b4b4(Te6edf3UTCTb4b4b4)Tff7b72.Td2a8ffisoformatTb4b4b4(Tb4b4b4)
Tff7b72with Tff7b72selfTff7b72.Td2a8ffproviderTb4b4b4:
Tff7b72selfTff7b72.Td2a8ffproviderTff7b72.Td2a8ffexecutemanyTb4b4b4(
T8b949e"""
INSERT INTO lxmf_conversation_read_state (destination_hash, last_read_at, created_at, updated_at)
VALUES (?, ?, ?, ?)
ON CONFLICT(destination_hash) DO UPDATE SET
last_read_at = EXCLUDED.last_read_at,
updated_at = EXCLUDED.updated_at
"""Tb4b4b4,
Tb4b4b4[Tb4b4b4(Te6edf3hTb4b4b4, Te6edf3nowTb4b4b4, Te6edf3nowTb4b4b4, Te6edf3nowTb4b4b4) Tff7b72for Te6edf3h Tff7b72in Te6edf3destination_hashesTb4b4b4]Tb4b4b4,
Tb4b4b4)

Tff7b72def Td2a8ffmark_all_conversations_as_readTb4b4b4(Tff7b72selfTb4b4b4)Tb4b4b4:
Te6edf3now Tff7b72= Te6edf3datetimeTff7b72.Td2a8ffnowTb4b4b4(Te6edf3UTCTb4b4b4)Tff7b72.Td2a8ffisoformatTb4b4b4(Tb4b4b4)
Tff7b72selfTff7b72.Td2a8ffproviderTff7b72.Td2a8ffexecuteTb4b4b4(
T8b949e"""
INSERT INTO lxmf_conversation_read_state (destination_hash, last_read_at, created_at, updated_at)
SELECT peer_hash, ?, ?, ? FROM lxmf_messages
WHERE peer_hash IS NOT NULL
GROUP BY peer_hash
ON CONFLICT(destination_hash) DO UPDATE SET
last_read_at = EXCLUDED.last_read_at,
updated_at = EXCLUDED.updated_at
"""Tb4b4b4,
Tb4b4b4(Te6edf3nowTb4b4b4, Te6edf3nowTb4b4b4, Te6edf3nowTb4b4b4)Tb4b4b4,
Tb4b4b4)

Tff7b72def Td2a8ffis_conversation_unreadTb4b4b4(Tff7b72selfTb4b4b4, Te6edf3destination_hashTb4b4b4)Tb4b4b4:
Te6edf3row Tff7b72= Tff7b72selfTff7b72.Td2a8ffproviderTff7b72.Td2a8fffetchoneTb4b4b4(
T8b949e"""
SELECT m.timestamp, r.last_read_at
FROM lxmf_messages m
LEFT JOIN lxmf_conversation_read_state r ON r.destination_hash = ?
WHERE m.peer_hash = ? AND m.is_incoming = 1
ORDER BY m.timestamp DESC LIMIT 1
"""Tb4b4b4,
Tb4b4b4(Te6edf3destination_hashTb4b4b4, Te6edf3destination_hashTb4b4b4)Tb4b4b4,
Tb4b4b4)

Tff7b72if Tff7b72not Te6edf3rowTb4b4b4:
Tff7b72return Tff7b72False
Tff7b72if Tff7b72not Te6edf3rowTb4b4b4[Ta5d6ff"Ta5d6fflast_read_atTa5d6ff"Tb4b4b4]Tb4b4b4:
Tff7b72return Tff7b72True

Te6edf3last_read_at Tff7b72= Te6edf3datetimeTff7b72.Td2a8fffromisoformatTb4b4b4(Te6edf3rowTb4b4b4[Ta5d6ff"Ta5d6fflast_read_atTa5d6ff"Tb4b4b4]Tb4b4b4)
Tff7b72if Te6edf3last_read_atTff7b72.Td2a8fftzinfo Tff7b72is Tff7b72NoneTb4b4b4:
Te6edf3last_read_at Tff7b72= Te6edf3last_read_atTff7b72.Td2a8ffreplaceTb4b4b4(Te6edf3tzinfoTff7b72=Te6edf3UTCTb4b4b4)

Tff7b72return Te6edf3rowTb4b4b4[Ta5d6ff"Ta5d6fftimestampTa5d6ff"Tb4b4b4] Tff7b72> Te6edf3last_read_atTff7b72.Td2a8fftimestampTb4b4b4(Tb4b4b4)

Tff7b72def Td2a8ffmark_stuck_messages_as_failedTb4b4b4(Tff7b72selfTb4b4b4)Tb4b4b4:
Tff7b72selfTff7b72.Td2a8ffproviderTff7b72.Td2a8ffexecuteTb4b4b4(
T8b949e"""
UPDATE lxmf_messages
SET state = 'failed', updated_at = ?
WHERE state = 'outbound'
OR (state = 'sent' AND method = 'opportunistic')
OR state = 'sending'
"""Tb4b4b4,
Tb4b4b4(Te6edf3datetimeTff7b72.Td2a8ffnowTb4b4b4(Te6edf3UTCTb4b4b4)Tff7b72.Td2a8ffisoformatTb4b4b4(Tb4b4b4)Tb4b4b4,Tb4b4b4)Tb4b4b4,
Tb4b4b4)

Tff7b72def Td2a8ffget_failed_messages_for_destinationTb4b4b4(Tff7b72selfTb4b4b4, Te6edf3destination_hashTb4b4b4)Tb4b4b4:
Tff7b72return Tff7b72selfTff7b72.Td2a8ffproviderTff7b72.Td2a8fffetchallTb4b4b4(
Ta5d6ff"Ta5d6ffSELECT * FROM lxmf_messages WHERE state = Ta5d6ff'Ta5d6fffailedTa5d6ff'Ta5d6ff AND peer_hash = ? ORDER BY id ASCTa5d6ff"Tb4b4b4,
Tb4b4b4(Te6edf3destination_hashTb4b4b4,Tb4b4b4)Tb4b4b4,
Tb4b4b4)

Tff7b72def Td2a8ffget_failed_messages_countTb4b4b4(Tff7b72selfTb4b4b4, Te6edf3destination_hashTb4b4b4)Tb4b4b4:
Te6edf3row Tff7b72= Tff7b72selfTff7b72.Td2a8ffproviderTff7b72.Td2a8fffetchoneTb4b4b4(
Ta5d6ff"Ta5d6ffSELECT COUNT(*) as count FROM lxmf_messages WHERE state = Ta5d6ff'Ta5d6fffailedTa5d6ff'Ta5d6ff AND peer_hash = ?Ta5d6ff"Tb4b4b4,
Tb4b4b4(Te6edf3destination_hashTb4b4b4,Tb4b4b4)Tb4b4b4,
Tb4b4b4)
Tff7b72return Te6edf3rowTb4b4b4[Ta5d6ff"Ta5d6ffcountTa5d6ff"Tb4b4b4] Tff7b72if Te6edf3row Tff7b72else T79c0ff0

Tff7b72def Td2a8ffget_conversations_unread_statesTb4b4b4(Tff7b72selfTb4b4b4, Te6edf3destination_hashesTb4b4b4)Tb4b4b4:
Tff7b72if Tff7b72not Te6edf3destination_hashesTb4b4b4:
Tff7b72return Tb4b4b4{Tb4b4b4}

Te6edf3placeholders Tff7b72= Ta5d6ff"Ta5d6ff, Ta5d6ff"Tff7b72.Td2a8ffjoinTb4b4b4(Tb4b4b4[Ta5d6ff"Ta5d6ff?Ta5d6ff"Tb4b4b4] Tff7b72* Tffa657lenTb4b4b4(Te6edf3destination_hashesTb4b4b4)Tb4b4b4)
Te6edf3query Tff7b72= Ta5d6fffTa5d6ff"""
Ta5d6ff SELECT peer_hash, MAX(timestamp) as latest_ts, last_read_at
Ta5d6ff FROM lxmf_messages m
Ta5d6ff LEFT JOIN lxmf_conversation_read_state r ON r.destination_hash = m.peer_hash
Ta5d6ff WHERE m.peer_hash IN (Tffd700{Te6edf3placeholdersTffd700}Ta5d6ff) AND m.is_incoming = 1
Ta5d6ff GROUP BY m.peer_hash
Ta5d6ff Ta5d6ff"""
Te6edf3rows Tff7b72= Tff7b72selfTff7b72.Td2a8ffproviderTff7b72.Td2a8fffetchallTb4b4b4(Te6edf3queryTb4b4b4, Te6edf3destination_hashesTb4b4b4)

Te6edf3unread_states Tff7b72= Tb4b4b4{Tb4b4b4}
Tff7b72for Te6edf3row Tff7b72in Te6edf3rowsTb4b4b4:
Te6edf3peer_hash Tff7b72= Te6edf3rowTb4b4b4[Ta5d6ff"Ta5d6ffpeer_hashTa5d6ff"Tb4b4b4]
Te6edf3latest_ts Tff7b72= Te6edf3rowTb4b4b4[Ta5d6ff"Ta5d6fflatest_tsTa5d6ff"Tb4b4b4]
Te6edf3last_read_at_str Tff7b72= Te6edf3rowTb4b4b4[Ta5d6ff"Ta5d6fflast_read_atTa5d6ff"Tb4b4b4]

Tff7b72if Tff7b72not Te6edf3last_read_at_strTb4b4b4:
Te6edf3unread_statesTb4b4b4[Te6edf3peer_hashTb4b4b4] Tff7b72= Tff7b72True
Tff7b72continue

Te6edf3last_read_at Tff7b72= Te6edf3datetimeTff7b72.Td2a8fffromisoformatTb4b4b4(Te6edf3last_read_at_strTb4b4b4)
Tff7b72if Te6edf3last_read_atTff7b72.Td2a8fftzinfo Tff7b72is Tff7b72NoneTb4b4b4:
Te6edf3last_read_at Tff7b72= Te6edf3last_read_atTff7b72.Td2a8ffreplaceTb4b4b4(Te6edf3tzinfoTff7b72=Te6edf3UTCTb4b4b4)

Te6edf3unread_statesTb4b4b4[Te6edf3peer_hashTb4b4b4] Tff7b72= Te6edf3latest_ts Tff7b72> Te6edf3last_read_atTff7b72.Td2a8fftimestampTb4b4b4(Tb4b4b4)

Tff7b72return Te6edf3unread_states

Tff7b72def Td2a8ffget_conversations_failed_countsTb4b4b4(Tff7b72selfTb4b4b4, Te6edf3destination_hashesTb4b4b4)Tb4b4b4:
Tff7b72if Tff7b72not Te6edf3destination_hashesTb4b4b4:
Tff7b72return Tb4b4b4{Tb4b4b4}
Te6edf3placeholders Tff7b72= Ta5d6ff"Ta5d6ff, Ta5d6ff"Tff7b72.Td2a8ffjoinTb4b4b4(Tb4b4b4[Ta5d6ff"Ta5d6ff?Ta5d6ff"Tb4b4b4] Tff7b72* Tffa657lenTb4b4b4(Te6edf3destination_hashesTb4b4b4)Tb4b4b4)
Te6edf3rows Tff7b72= Tff7b72selfTff7b72.Td2a8ffproviderTff7b72.Td2a8fffetchallTb4b4b4(
Ta5d6fffTa5d6ff"Ta5d6ffSELECT peer_hash, COUNT(*) as count FROM lxmf_messages WHERE state = Ta5d6ff'Ta5d6fffailedTa5d6ff'Ta5d6ff AND peer_hash IN (Tffd700{Te6edf3placeholdersTffd700}Ta5d6ff) GROUP BY peer_hashTa5d6ff"Tb4b4b4,
Tffa657tupleTb4b4b4(Te6edf3destination_hashesTb4b4b4)Tb4b4b4,
Tb4b4b4)
Tff7b72return Tb4b4b4{Te6edf3rowTb4b4b4[Ta5d6ff"Ta5d6ffpeer_hashTa5d6ff"Tb4b4b4]Tb4b4b4: Te6edf3rowTb4b4b4[Ta5d6ff"Ta5d6ffcountTa5d6ff"Tb4b4b4] Tff7b72for Te6edf3row Tff7b72in Te6edf3rowsTb4b4b4}

Tff7b72def Td2a8ffget_conversations_attachment_statesTb4b4b4(Tff7b72selfTb4b4b4, Te6edf3destination_hashesTb4b4b4)Tb4b4b4:
Tff7b72if Tff7b72not Te6edf3destination_hashesTb4b4b4:
Tff7b72return Tb4b4b4{Tb4b4b4}

Te6edf3placeholders Tff7b72= Ta5d6ff"Ta5d6ff, Ta5d6ff"Tff7b72.Td2a8ffjoinTb4b4b4(Tb4b4b4[Ta5d6ff"Ta5d6ff?Ta5d6ff"Tb4b4b4] Tff7b72* Tffa657lenTb4b4b4(Te6edf3destination_hashesTb4b4b4)Tb4b4b4)
Te6edf3query Tff7b72= Ta5d6fffTa5d6ff"""
Ta5d6ff SELECT peer_hash, 1 as has_attachments
Ta5d6ff FROM lxmf_messages
Ta5d6ff WHERE peer_hash IN (Tffd700{Te6edf3placeholdersTffd700}Ta5d6ff)
Ta5d6ff AND fields IS NOT NULL AND fields != Ta5d6ff'Tffea00{{Tffea00}}Ta5d6ff'Ta5d6ff AND fields != Ta5d6ff'Ta5d6ff'
Ta5d6ff GROUP BY peer_hash
Ta5d6ff Ta5d6ff"""
Te6edf3rows Tff7b72= Tff7b72selfTff7b72.Td2a8ffproviderTff7b72.Td2a8fffetchallTb4b4b4(Te6edf3queryTb4b4b4, Te6edf3destination_hashesTb4b4b4)

Tff7b72return Tb4b4b4{Te6edf3rowTb4b4b4[Ta5d6ff"Ta5d6ffpeer_hashTa5d6ff"Tb4b4b4]Tb4b4b4: Tff7b72True Tff7b72for Te6edf3row Tff7b72in Te6edf3rowsTb4b4b4}

T8b949e# Forwarding Mappings
Tff7b72def Td2a8ffget_forwarding_mappingTb4b4b4(
Tff7b72selfTb4b4b4,
Te6edf3alias_hashTff7b72=Tff7b72NoneTb4b4b4,
Te6edf3original_sender_hashTff7b72=Tff7b72NoneTb4b4b4,
Te6edf3final_recipient_hashTff7b72=Tff7b72NoneTb4b4b4,
Tb4b4b4)Tb4b4b4:
Tff7b72if Te6edf3alias_hashTb4b4b4:
Tff7b72return Tff7b72selfTff7b72.Td2a8ffproviderTff7b72.Td2a8fffetchoneTb4b4b4(
Ta5d6ff"Ta5d6ffSELECT * FROM lxmf_forwarding_mappings WHERE alias_hash = ?Ta5d6ff"Tb4b4b4,
Tb4b4b4(Te6edf3alias_hashTb4b4b4,Tb4b4b4)Tb4b4b4,
Tb4b4b4)
Tff7b72if Te6edf3original_sender_hash Tff7b72and Te6edf3final_recipient_hashTb4b4b4:
Tff7b72return Tff7b72selfTff7b72.Td2a8ffproviderTff7b72.Td2a8fffetchoneTb4b4b4(
Ta5d6ff"Ta5d6ffSELECT * FROM lxmf_forwarding_mappings WHERE original_sender_hash = ? AND final_recipient_hash = ?Ta5d6ff"Tb4b4b4,
Tb4b4b4(Te6edf3original_sender_hashTb4b4b4, Te6edf3final_recipient_hashTb4b4b4)Tb4b4b4,
Tb4b4b4)
Tff7b72return Tff7b72None

Tff7b72def Td2a8ffcreate_forwarding_mappingTb4b4b4(Tff7b72selfTb4b4b4, Te6edf3dataTb4b4b4)Tb4b4b4:
T8b949e# Ensure data is a dict if it's a sqlite3.Row
Tff7b72if Tff7b72not Tffa657isinstanceTb4b4b4(Te6edf3dataTb4b4b4, Tffa657dictTb4b4b4)Tb4b4b4:
Te6edf3data Tff7b72= Tffa657dictTb4b4b4(Te6edf3dataTb4b4b4)

Te6edf3fields Tff7b72= Tb4b4b4[
Ta5d6ff"Ta5d6ffalias_identity_private_keyTa5d6ff"Tb4b4b4,
Ta5d6ff"Ta5d6ffalias_hashTa5d6ff"Tb4b4b4,
Ta5d6ff"Ta5d6fforiginal_sender_hashTa5d6ff"Tb4b4b4,
Ta5d6ff"Ta5d6fffinal_recipient_hashTa5d6ff"Tb4b4b4,
Ta5d6ff"Ta5d6fforiginal_destination_hashTa5d6ff"Tb4b4b4,
Tb4b4b4]
Te6edf3columns Tff7b72= Ta5d6ff"Ta5d6ff, Ta5d6ff"Tff7b72.Td2a8ffjoinTb4b4b4(Te6edf3fieldsTb4b4b4)
Te6edf3placeholders Tff7b72= Ta5d6ff"Ta5d6ff, Ta5d6ff"Tff7b72.Td2a8ffjoinTb4b4b4(Tb4b4b4[Ta5d6ff"Ta5d6ff?Ta5d6ff"Tb4b4b4] Tff7b72* Tffa657lenTb4b4b4(Te6edf3fieldsTb4b4b4)Tb4b4b4)
Te6edf3query Tff7b72= Ta5d6fffTa5d6ff"Ta5d6ffINSERT INTO lxmf_forwarding_mappings (Tffd700{Te6edf3columnsTffd700}Ta5d6ff, created_at) VALUES (Tffd700{Te6edf3placeholdersTffd700}Ta5d6ff, ?)Ta5d6ff"
Te6edf3params Tff7b72= Tb4b4b4[Te6edf3dataTff7b72.Td2a8ffgetTb4b4b4(Te6edf3fTb4b4b4) Tff7b72for Te6edf3f Tff7b72in Te6edf3fieldsTb4b4b4]
Te6edf3paramsTff7b72.Td2a8ffappendTb4b4b4(Te6edf3datetimeTff7b72.Td2a8ffnowTb4b4b4(Te6edf3UTCTb4b4b4)Tff7b72.Td2a8ffisoformatTb4b4b4(Tb4b4b4)Tb4b4b4)
Tff7b72selfTff7b72.Td2a8ffproviderTff7b72.Td2a8ffexecuteTb4b4b4(Te6edf3queryTb4b4b4, Te6edf3paramsTb4b4b4)

Tff7b72def Td2a8ffget_all_forwarding_mappingsTb4b4b4(Tff7b72selfTb4b4b4)Tb4b4b4:
Tff7b72return Tff7b72selfTff7b72.Td2a8ffproviderTff7b72.Td2a8fffetchallTb4b4b4(Ta5d6ff"Ta5d6ffSELECT * FROM lxmf_forwarding_mappingsTa5d6ff"Tb4b4b4)

Tff7b72def Td2a8ffmark_notification_as_viewedTb4b4b4(Tff7b72selfTb4b4b4, Te6edf3destination_hashTb4b4b4)Tb4b4b4:
Te6edf3now Tff7b72= Te6edf3datetimeTff7b72.Td2a8ffnowTb4b4b4(Te6edf3UTCTb4b4b4)Tff7b72.Td2a8ffisoformatTb4b4b4(Tb4b4b4)
Tff7b72selfTff7b72.Td2a8ffproviderTff7b72.Td2a8ffexecuteTb4b4b4(
T8b949e"""
INSERT INTO notification_viewed_state (destination_hash, last_viewed_at, created_at, updated_at)
VALUES (?, ?, ?, ?)
ON CONFLICT(destination_hash) DO UPDATE SET
last_viewed_at = EXCLUDED.last_viewed_at,
updated_at = EXCLUDED.updated_at
"""Tb4b4b4,
Tb4b4b4(Te6edf3destination_hashTb4b4b4, Te6edf3nowTb4b4b4, Te6edf3nowTb4b4b4, Te6edf3nowTb4b4b4)Tb4b4b4,
Tb4b4b4)

Tff7b72def Td2a8ffmark_all_notifications_as_viewedTb4b4b4(Tff7b72selfTb4b4b4, Te6edf3destination_hashesTff7b72=Tff7b72NoneTb4b4b4)Tb4b4b4:
Te6edf3now Tff7b72= Te6edf3datetimeTff7b72.Td2a8ffnowTb4b4b4(Te6edf3UTCTb4b4b4)Tff7b72.Td2a8ffisoformatTb4b4b4(Tb4b4b4)
Tff7b72if Te6edf3destination_hashesTb4b4b4:
Tff7b72with Tff7b72selfTff7b72.Td2a8ffproviderTb4b4b4:
Tff7b72selfTff7b72.Td2a8ffproviderTff7b72.Td2a8ffexecutemanyTb4b4b4(
T8b949e"""
INSERT INTO notification_viewed_state (destination_hash, last_viewed_at, created_at, updated_at)
VALUES (?, ?, ?, ?)
ON CONFLICT(destination_hash) DO UPDATE SET
last_viewed_at = EXCLUDED.last_viewed_at,
updated_at = EXCLUDED.updated_at
"""Tb4b4b4,
Tb4b4b4[Tb4b4b4(Te6edf3hTb4b4b4, Te6edf3nowTb4b4b4, Te6edf3nowTb4b4b4, Te6edf3nowTb4b4b4) Tff7b72for Te6edf3h Tff7b72in Te6edf3destination_hashesTb4b4b4]Tb4b4b4,
Tb4b4b4)
Tff7b72elseTb4b4b4:
T8b949e# mark all conversations as viewed
Tff7b72selfTff7b72.Td2a8ffproviderTff7b72.Td2a8ffexecuteTb4b4b4(
T8b949e"""
INSERT INTO notification_viewed_state (destination_hash, last_viewed_at, created_at, updated_at)
SELECT peer_hash, ?, ?, ? FROM lxmf_messages
WHERE peer_hash IS NOT NULL
GROUP BY peer_hash
ON CONFLICT(destination_hash) DO UPDATE SET
last_viewed_at = EXCLUDED.last_viewed_at,
updated_at = EXCLUDED.updated_at
"""Tb4b4b4,
Tb4b4b4(Te6edf3nowTb4b4b4, Te6edf3nowTb4b4b4, Te6edf3nowTb4b4b4)Tb4b4b4,
Tb4b4b4)

Tff7b72def Td2a8ffis_notification_viewedTb4b4b4(Tff7b72selfTb4b4b4, Te6edf3destination_hashTb4b4b4, Te6edf3message_timestampTb4b4b4)Tb4b4b4:
Te6edf3row Tff7b72= Tff7b72selfTff7b72.Td2a8ffproviderTff7b72.Td2a8fffetchoneTb4b4b4(
Ta5d6ff"Ta5d6ffSELECT last_viewed_at FROM notification_viewed_state WHERE destination_hash = ?Ta5d6ff"Tb4b4b4,
Tb4b4b4(Te6edf3destination_hashTb4b4b4,Tb4b4b4)Tb4b4b4,
Tb4b4b4)
Tff7b72if Tff7b72not Te6edf3row Tff7b72or Tff7b72not Te6edf3rowTb4b4b4[Ta5d6ff"Ta5d6fflast_viewed_atTa5d6ff"Tb4b4b4]Tb4b4b4:
Tff7b72return Tff7b72False

Te6edf3last_viewed_at Tff7b72= Te6edf3datetimeTff7b72.Td2a8fffromisoformatTb4b4b4(Te6edf3rowTb4b4b4[Ta5d6ff"Ta5d6fflast_viewed_atTa5d6ff"Tb4b4b4]Tb4b4b4)
Tff7b72if Te6edf3last_viewed_atTff7b72.Td2a8fftzinfo Tff7b72is Tff7b72NoneTb4b4b4:
Te6edf3last_viewed_at Tff7b72= Te6edf3last_viewed_atTff7b72.Td2a8ffreplaceTb4b4b4(Te6edf3tzinfoTff7b72=Te6edf3UTCTb4b4b4)

Tff7b72return Te6edf3message_timestamp Tff7b72<Tff7b72= Te6edf3last_viewed_atTff7b72.Td2a8fftimestampTb4b4b4(Tb4b4b4)

T8b949e# Folders
Tff7b72def Td2a8ffget_all_foldersTb4b4b4(Tff7b72selfTb4b4b4)Tb4b4b4:
Tff7b72return Tff7b72selfTff7b72.Td2a8ffproviderTff7b72.Td2a8fffetchallTb4b4b4(Ta5d6ff"Ta5d6ffSELECT * FROM lxmf_folders ORDER BY name ASCTa5d6ff"Tb4b4b4)

Tff7b72def Td2a8ffcreate_folderTb4b4b4(Tff7b72selfTb4b4b4, Te6edf3nameTb4b4b4)Tb4b4b4:
Te6edf3now Tff7b72= Te6edf3datetimeTff7b72.Td2a8ffnowTb4b4b4(Te6edf3UTCTb4b4b4)Tff7b72.Td2a8ffisoformatTb4b4b4(Tb4b4b4)
Tff7b72return Tff7b72selfTff7b72.Td2a8ffproviderTff7b72.Td2a8ffexecuteTb4b4b4(
Ta5d6ff"Ta5d6ffINSERT INTO lxmf_folders (name, created_at, updated_at) VALUES (?, ?, ?)Ta5d6ff"Tb4b4b4,
Tb4b4b4(Te6edf3nameTb4b4b4, Te6edf3nowTb4b4b4, Te6edf3nowTb4b4b4)Tb4b4b4,
Tb4b4b4)

Tff7b72def Td2a8ffrename_folderTb4b4b4(Tff7b72selfTb4b4b4, Te6edf3folder_idTb4b4b4, Te6edf3new_nameTb4b4b4)Tb4b4b4:
Te6edf3now Tff7b72= Te6edf3datetimeTff7b72.Td2a8ffnowTb4b4b4(Te6edf3UTCTb4b4b4)Tff7b72.Td2a8ffisoformatTb4b4b4(Tb4b4b4)
Tff7b72selfTff7b72.Td2a8ffproviderTff7b72.Td2a8ffexecuteTb4b4b4(
Ta5d6ff"Ta5d6ffUPDATE lxmf_folders SET name = ?, updated_at = ? WHERE id = ?Ta5d6ff"Tb4b4b4,
Tb4b4b4(Te6edf3new_nameTb4b4b4, Te6edf3nowTb4b4b4, Te6edf3folder_idTb4b4b4)Tb4b4b4,
Tb4b4b4)

Tff7b72def Td2a8ffdelete_folderTb4b4b4(Tff7b72selfTb4b4b4, Te6edf3folder_idTb4b4b4)Tb4b4b4:
Tff7b72selfTff7b72.Td2a8ffproviderTff7b72.Td2a8ffexecuteTb4b4b4(Ta5d6ff"Ta5d6ffDELETE FROM lxmf_folders WHERE id = ?Ta5d6ff"Tb4b4b4, Tb4b4b4(Te6edf3folder_idTb4b4b4,Tb4b4b4)Tb4b4b4)

Tff7b72def Td2a8ffget_conversation_folderTb4b4b4(Tff7b72selfTb4b4b4, Te6edf3peer_hashTb4b4b4)Tb4b4b4:
Tff7b72return Tff7b72selfTff7b72.Td2a8ffproviderTff7b72.Td2a8fffetchoneTb4b4b4(
Ta5d6ff"Ta5d6ffSELECT * FROM lxmf_conversation_folders WHERE peer_hash = ?Ta5d6ff"Tb4b4b4,
Tb4b4b4(Te6edf3peer_hashTb4b4b4,Tb4b4b4)Tb4b4b4,
Tb4b4b4)

Tff7b72def Td2a8ffmove_conversation_to_folderTb4b4b4(Tff7b72selfTb4b4b4, Te6edf3peer_hashTb4b4b4, Te6edf3folder_idTb4b4b4)Tb4b4b4:
Te6edf3now Tff7b72= Te6edf3datetimeTff7b72.Td2a8ffnowTb4b4b4(Te6edf3UTCTb4b4b4)Tff7b72.Td2a8ffisoformatTb4b4b4(Tb4b4b4)
Tff7b72if Te6edf3folder_id Tff7b72is Tff7b72NoneTb4b4b4:
Tff7b72selfTff7b72.Td2a8ffproviderTff7b72.Td2a8ffexecuteTb4b4b4(
Ta5d6ff"Ta5d6ffDELETE FROM lxmf_conversation_folders WHERE peer_hash = ?Ta5d6ff"Tb4b4b4,
Tb4b4b4(Te6edf3peer_hashTb4b4b4,Tb4b4b4)Tb4b4b4,
Tb4b4b4)
Tff7b72elseTb4b4b4:
Tff7b72selfTff7b72.Td2a8ffproviderTff7b72.Td2a8ffexecuteTb4b4b4(
T8b949e"""
INSERT INTO lxmf_conversation_folders (peer_hash, folder_id, created_at, updated_at)
VALUES (?, ?, ?, ?)
ON CONFLICT(peer_hash) DO UPDATE SET
folder_id = EXCLUDED.folder_id,
updated_at = EXCLUDED.updated_at
"""Tb4b4b4,
Tb4b4b4(Te6edf3peer_hashTb4b4b4, Te6edf3folder_idTb4b4b4, Te6edf3nowTb4b4b4, Te6edf3nowTb4b4b4)Tb4b4b4,
Tb4b4b4)

Tff7b72def Td2a8ffmove_conversations_to_folderTb4b4b4(Tff7b72selfTb4b4b4, Te6edf3peer_hashesTb4b4b4, Te6edf3folder_idTb4b4b4)Tb4b4b4:
Tff7b72if Tff7b72not Te6edf3peer_hashesTb4b4b4:
Tff7b72return
Te6edf3now Tff7b72= Te6edf3datetimeTff7b72.Td2a8ffnowTb4b4b4(Te6edf3UTCTb4b4b4)Tff7b72.Td2a8ffisoformatTb4b4b4(Tb4b4b4)
Tff7b72with Tff7b72selfTff7b72.Td2a8ffproviderTb4b4b4:
Tff7b72if Te6edf3folder_id Tff7b72is Tff7b72NoneTb4b4b4:
Te6edf3placeholders Tff7b72= Ta5d6ff"Ta5d6ff, Ta5d6ff"Tff7b72.Td2a8ffjoinTb4b4b4(Tb4b4b4[Ta5d6ff"Ta5d6ff?Ta5d6ff"Tb4b4b4] Tff7b72* Tffa657lenTb4b4b4(Te6edf3peer_hashesTb4b4b4)Tb4b4b4)
Tff7b72selfTff7b72.Td2a8ffproviderTff7b72.Td2a8ffexecuteTb4b4b4(
Ta5d6fffTa5d6ff"Ta5d6ffDELETE FROM lxmf_conversation_folders WHERE peer_hash IN (Tffd700{Te6edf3placeholdersTffd700}Ta5d6ff)Ta5d6ff"Tb4b4b4,
Tffa657tupleTb4b4b4(Te6edf3peer_hashesTb4b4b4)Tb4b4b4,
Tb4b4b4)
Tff7b72elseTb4b4b4:
Tff7b72selfTff7b72.Td2a8ffproviderTff7b72.Td2a8ffexecutemanyTb4b4b4(
T8b949e"""
INSERT INTO lxmf_conversation_folders (peer_hash, folder_id, created_at, updated_at)
VALUES (?, ?, ?, ?)
ON CONFLICT(peer_hash) DO UPDATE SET
folder_id = EXCLUDED.folder_id,
updated_at = EXCLUDED.updated_at
"""Tb4b4b4,
Tb4b4b4[Tb4b4b4(Te6edf3hTb4b4b4, Te6edf3folder_idTb4b4b4, Te6edf3nowTb4b4b4, Te6edf3nowTb4b4b4) Tff7b72for Te6edf3h Tff7b72in Te6edf3peer_hashesTb4b4b4]Tb4b4b4,
Tb4b4b4)

Tff7b72def Td2a8ffget_all_conversation_foldersTb4b4b4(Tff7b72selfTb4b4b4)Tb4b4b4:
Tff7b72return Tff7b72selfTff7b72.Td2a8ffproviderTff7b72.Td2a8fffetchallTb4b4b4(Ta5d6ff"Ta5d6ffSELECT * FROM lxmf_conversation_foldersTa5d6ff"Tb4b4b4)


──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────